#include "DatabaseConnection.h"
#include <tchar.h>

void DataBaseConnection::Initialize() throw ( DataBaseException )
{
    SQLRETURN ret = SQL_SUCCESS;

    ret = SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_environmentHandle );
    
    if( CHECK_SQL_FAILED( ret ) )
    {
        throw DataBaseException("Failed to create database environment handle");
    }
   
    //TODO this is specialised for ODBC3, make it generic in future
    ret = SQLSetEnvAttr( m_environmentHandle, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0 );

    if( CHECK_SQL_FAILED( ret ) )
    {
        throw DataBaseException( "Failed to set environment parameter" );
    }

    ret = SQLAllocHandle( SQL_HANDLE_DBC, m_environmentHandle, &m_connectionHandle );
    if( CHECK_SQL_FAILED( ret ) )
    {
        throw DataBaseException( "Failed to create connection handle" );
    }

}

#define CPCHAR2SQLCHAR( a ) (SQLCHAR*)(const_cast<char*>((a)))

void DataBaseConnection::Connect( const char* serverName, const char* userName, const char* authentication ) throw( DataBaseException )
{
    SQLRETURN ret = SQL_SUCCESS;

    ret = SQLConnectA( 
        m_connectionHandle, 
        CPCHAR2SQLCHAR(serverName), 
        strlen( serverName ), 
        CPCHAR2SQLCHAR( userName ),
        strlen( userName ),
        CPCHAR2SQLCHAR( authentication ),
        strlen( authentication ) );

    if( CHECK_SQL_FAILED( ret ) )
    {
        throw DataBaseException( "Failed to connect to server" );
    }
}

#define CPWCHAR2SQLWCHAR( a ) (SQLWCHAR*)(const_cast<wchar_t*>((a)))
void DataBaseConnection::Connect( const wchar_t* serverName, const wchar_t* userName, const wchar_t* authentication ) throw( DataBaseException )
{
    SQLRETURN ret = SQL_SUCCESS;

    ret = SQLConnectW( 
        m_connectionHandle, 
        CPWCHAR2SQLWCHAR(serverName), 
        wcslen( serverName ), 
        CPWCHAR2SQLWCHAR( userName ),
        wcslen( userName ),
        CPWCHAR2SQLWCHAR( authentication ),
        wcslen( authentication ) );

    if( CHECK_SQL_FAILED( ret ) )
    {
        throw DataBaseException( "Failed to connect to server" );
    }
}

void DataBaseConnection::DisConnect()
{
    SQLRETURN ret = SQLDisconnect(m_connectionHandle);
    if( CHECK_SQL_FAILED( ret ) )
    {
        throw DataBaseException( "Failed to disconnect from server" );
    }
}

void DataBaseConnection::UnInitialize()
{
    SQLRETURN ret = SQLFreeHandle( SQL_HANDLE_DBC, m_connectionHandle );
    if( CHECK_SQL_FAILED( ret ) )
    {
        throw DataBaseException( "Failed to free connection handle" );
    }

    ret = SQLFreeHandle( SQL_HANDLE_ENV, m_environmentHandle );
    if( CHECK_SQL_FAILED( ret ) )
    {
        throw DataBaseException( "Failed to free environment handle" );
    }
}

std::shared_ptr<DataBaseStatement> DataBaseConnection::CreateStatement( const wchar_t* statement )
{
    SQLRETURN ret = SQL_SUCCESS;
    SQLHANDLE stHandle = SQL_NULL_HANDLE;

    ret = SQLAllocHandle( SQL_HANDLE_STMT, m_connectionHandle, &stHandle );
    if( CHECK_SQL_FAILED( ret ) )
    {
        throw DataBaseException( "Failed to create statement handle" );
    }

    std::shared_ptr<DataBaseStatement> dbStatementSmtPtr( new DataBaseStatement( stHandle, statement ) );
    return dbStatementSmtPtr;
}

std::shared_ptr<DataBaseStatement> DataBaseConnection::CreateStatement()
{
    SQLRETURN ret = SQL_SUCCESS;
    SQLHANDLE stHandle = SQL_NULL_HANDLE;

    ret = SQLAllocHandle( SQL_HANDLE_STMT, m_connectionHandle, &stHandle );
    if( CHECK_SQL_FAILED( ret ) )
    {
        throw DataBaseException( "Failed to create statement handle" );
    }

    std::shared_ptr<DataBaseStatement> dbStatementSmtPtr( new DataBaseStatement() );
    dbStatementSmtPtr->SetHandle( stHandle );
    return dbStatementSmtPtr;
}

void DataBaseStatement::Prepare()
{
    SQLRETURN ret = SQLPrepareW( m_statementHandle, const_cast<wchar_t*>( m_statement.c_str() ), m_statement.length() );
    
    if( CHECK_SQL_FAILED( ret ) )
    {
        throw DataBaseException( "Failed to prepare the statement" );
    }

    m_prepared = true;
}


void DataBaseStatement::Execute()
{
    if( m_statement.size() == 0 )
        throw DataBaseException( "Statement is empty" );
    if( m_statementHandle == SQL_NULL_HANDLE )
        throw DataBaseException( "Statement Handle is null" );
   
    SQLRETURN ret = SQL_SUCCESS;

    if( m_prepared )
        ret = SQLExecute( m_statementHandle );
    else
        ret = SQLExecDirectW( m_statementHandle, const_cast<wchar_t*>( m_statement.c_str() ), m_statement.length() );
       
    if( CHECK_SQL_FAILED( ret ) )
    {
        throw DataBaseException( "Failed to execute statement, SQLExecDirect" );
    }

}

bool DataBaseStatement::FetchPrev()
{
    if( SQLFetchScroll( GetHandle(), SQL_FETCH_PREV, 0 ) != SQL_NO_DATA )
    {
        return true;
    }
    return false;
}

bool DataBaseStatement::FetchNext()
{
    if( SQLFetchScroll( GetHandle(), SQL_FETCH_NEXT, 0 ) != SQL_NO_DATA )
        return true;
    return false;
}

bool DataBaseStatement::FetchNextEx()
{
    SQLRETURN ret = SQLFetchScroll( GetHandle(), SQL_FETCH_NEXT, 0 );
    if( ret == SQL_NO_DATA )
        return false;
    if( ret == SQL_SUCCESS )
        return false;
    if( ret == SQL_SUCCESS_WITH_INFO )
    {
        SQLSTATE sqlState;
        SQLINTEGER sqlErrorCode;
        SQLRETURN ret = SQLGetDiagRec( SQL_HANDLE_STMT, GetHandle(), 1, sqlState, &sqlErrorCode, NULL, 0, NULL );
        if( _tcsncmp( sqlState, _T("01004"), 5 ) 
            || _tcsncmp( sqlState, _T( "01S07" ), 5 )
            || _tcsncmp( sqlState, _T( "22001" ), 5 ) )
        {
            //Truncation occored, resize the column
            std::cout << "Should resize the column" << std::endl;
        }
    }

}

bool DataBaseStatement::FetchLast()
{
    if( SQLFetchScroll( GetHandle(), SQL_FETCH_LAST, 0 ) != SQL_NO_DATA )
        return true;
    return false;
}

void DataBaseStatement::FetchEnd()
{
    SQLCloseCursor( GetHandle() );
}

int DataBaseStatement::RowNumberAffected() throw( DataBaseException)
{
    SQLINTEGER rowsAffected = 0;
    SQLRETURN ret = SQLRowCount( m_statementHandle, &rowsAffected );
    if( CHECK_SQL_FAILED( ret ) )
    {
        throw DataBaseException( "Failed to get affected row number" );
    }
    return rowsAffected;
}
    
int DataBaseStatement::ColumnNumberRetured() throw( DataBaseException )
{
    SQLSMALLINT columnReturned = 0;
    SQLRETURN ret = SQLNumResultCols( m_statementHandle, &columnReturned );
    if( CHECK_SQL_FAILED( ret ) )
    {
        throw DataBaseException( "Failed to get result column numbers" );
    }
    return columnReturned;
}

void DataBaseConnection::GetInfo( SQLUSMALLINT infoType, std::vector<char>& infoVal, bool resize )
{
    SQLSMALLINT stringLen = 0;
    
    SQLRETURN ret = SQLGetInfo( m_connectionHandle, infoType, &infoVal[0], infoVal.size(), &stringLen );
    if( ret == SQL_SUCCESS_WITH_INFO )
    {
        SQLSTATE sqlState;
        SQLINTEGER  nativeCode = 0;
        SQLSMALLINT textLength = 0;

        //TODO check how to fix this SQLGetDiagField( 
        SQLGetDiagRec( 
            SQL_HANDLE_DBC, 
            m_connectionHandle, 
            1,
            sqlState,
            &nativeCode,
            NULL,
            0,
            &textLength );
        
    }

}

void DataBaseStatement::BindParameter( 
        int paramIndex, 
        StatementParam* param ) 
{
    static SQLSMALLINT IOType2ODBC[Param_Max] = 
    {
        SQL_PARAM_INPUT,//Param_Input = 0,
        SQL_PARAM_OUTPUT,//Param_Output,
        SQL_PARAM_INPUT_OUTPUT,//Param_InputOutput,
    };
    SQLRETURN ret = SQLBindParameter( GetHandle(), 
        paramIndex,
        (SQLSMALLINT)IOType2ODBC[ param->m_paramType ],
        (SQLSMALLINT)param->m_cDataType,
        (SQLSMALLINT)param->m_sqlValueType,
        param->m_columnSize,
        (SQLSMALLINT)param->m_decimalDef,
        param->m_buffer.data(),
        param->m_buffer.size(),
        &param->m_strLenOrIndPtr );
    
    if( CHECK_SQL_FAILED( ret ) )
    {
        throw DataBaseException( "Failed to bind the parameter" );
    }
}
